package terra.managers;

import static terra.managers.DefManager.bioms;
import static terra.managers.DefManager.groundDefs;

import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;

import terra.Config;
import terra.database.DatabaseFactory;
import terra.database.SqlUtils;
import terra.definitions.Biom;
import terra.definitions.Biom.BiomGroundDef;
import terra.definitions.Biom.BiomObjectDef;
import terra.definitions.GroundDef;
import terra.definitions.NpcDef;
import terra.model.SpawnPoint;
import terra.model.World;
import terra.model.map.Map;
import terra.model.object.Flora;
import terra.model.object.GameObject;
import terra.model.object.LightObject;
import terra.model.object.NpcObject;
import terra.model.object.StaticObject;
import terra.model.object.Tree;
import terra.model.object.map.AbstractMapObject;
import terra.model.object.map.GroundObject;
import terra.model.object.map.MapObject;
import terra.model.runnable.WaterSimulater;
import terra.model.runnable.WaterTileChangesBroadcaster;
import terra.util.MathUtil;

public class MapManager implements Runnable {

	/**
	 * Минимально существующий индекс карты. Для удобства подгрузки\создания
	 * новых карт
	 */
	public int minMapIndex;
	/**
	 * Максимально существующий индекс карты. Для удобства подгрузки\создания
	 * новых карт
	 */
	public int maxMapIndex;
	/**
	 * Расстояние до границы карт для сохранения и удаления карты.
	 */
	private ArrayList<Integer> waitingMaps = new ArrayList<Integer>();
	/**
	 * Уровень воды в блоках. Является средним числом из начальных и конечных
	 * высот всех карт.
	 */
	public int waterLine = Config.mapHeight/2;

	/** Список карт */
	private ConcurrentHashMap<Integer, Map> maps;

	public boolean init = false;
	public static boolean newGame = true;

	Thread waterSimulator;
	Thread groundChecker, waterChecker;
	Thread mapManager;

	public MapManager() {
		init();
	}

	public void init() {
		mapManager = new Thread(this, "MapManager Thread");
		mapManager.setPriority(Thread.MIN_PRIORITY);
		mapManager.start();
	}

	private void initManager() {
		System.out.println("MapManager :  init start---------");
		SqlUtils.emptyTables();
		setMaps(new ConcurrentHashMap<Integer, Map>());
		File dir = new File("map");
		if (dir.exists()) {
			if (loadWorld(dir)){
				loadNpcs();
				loadStaticObjects();
				loadWaterLine();
				//calcWaterLine();
			}
		} else {
			createWorld(dir);
		}

		for (Map m : getMaps().values())
			m.deactivate();
		waterSimulator = new Thread(new WaterSimulater(),
				"Water Simulator Thread");
		waterSimulator.setPriority(Thread.MIN_PRIORITY);
		waterSimulator.start();
		init = true;
		World.getInstance().init();
		ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(
				new WaterTileChangesBroadcaster(), 0, 250L);

		System.out.println("MapManager :  initialized---------");
		System.gc();

	}

	private void loadWaterLine() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("SELECT value FROM utils WHERE name=?")) {
			statement.setString(1, "waterline");
			ResultSet rs = statement.executeQuery();
			rs.next();
			waterLine = rs.getInt(1);
			rs.close();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("Waterline loaded: "+ waterLine);
	}

	private void calcWaterLine() {
		int total = 0;
		int count = 0;
		for (Map m : getMaps().values()) {
			for (int i = 0; i < Config.mapWidth; i++) {
				total += m.getGroundLine()[i];
			}
			count += Config.mapWidth;
		}
		waterLine = total / count+32;
		System.out.println("Waterline calculated: "+ waterLine);
		
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("REPLACE INTO utils (name,value) VALUES (?,?)");) {
			statement.setString(1, "waterline");
			statement.setString(2, waterLine+"");			
			statement.execute();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private boolean createWorld(File dir) {
		try{
			dir.mkdir();
			minMapIndex = -Config.initialSize;
			for (int i = -Config.initialSize; i < Config.initialSize; i++) {
				maxMapIndex = i;
				createMap(i);
			}
	
			for (Map m : getMaps().values()) {
				setBiom(m);
			}
			for (Map m : getMaps().values()) {
				applyBiom(m);
			}
			for (Map m : getMaps().values()) {
				generateVerticalCave(m);
			}
			for (Map m : getMaps().values()) {
				for (int x = 0; x < Config.mapWidth; x++) {
					World.getInstance().groundListener.checkGround(m.index, x);
				}
			}
			for (Map m : getMaps().values()) {
				generateWater(m);
			}
			calcWaterLine();	
			for (int i = 0; i < Config.waterSimulationTime; i++) {
				for (int j = minMapIndex; j <= maxMapIndex; j++) {
					WaterSimulater.simulateWaterPhysics(getMaps().get(j), 1);
				}
			}
			for (Map m : MapManager.getInstance().getMaps().values())
				m.save();
			
//			for(GameObject obj: World.getInstance().getObjects().values()){
//				if (obj instanceof NpcObject){
//					((NpcObject) obj).insertNpc();
//				} else if (obj instanceof StaticObject){
//					((StaticObject) obj).insertStaticObject();
//				}
//			}
		} catch (Exception e){
			e.printStackTrace();
			System.out.println("Create world failed. System exit.");
			System.exit(0);
			
			return false;
		}
		return true;
	}

	private boolean loadWorld(File dir) {
		for (File file : dir.listFiles()) {
			Map map = new Map();
			map.index = Integer.parseInt(file.getName().split(".map")[0]);
			map.load();
			if (map.index < minMapIndex)
				minMapIndex = map.index;
			if (map.index > maxMapIndex)
				maxMapIndex = map.index;
			getMaps().put(map.index, map);
		}

		for (Map m : getMaps().values()) {
			WeatherManager.getInstance().mapCreated(m);
		}


		return true;

	}

	private void loadNpcs() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("SELECT * FROM npcs")) {
			ResultSet rs = statement.executeQuery();
			while (rs.next()) {
				NpcObject npc = NpcObject.loadNpc(rs);
				if (getMap(npc.getMapIndex()) == null)
					continue;
				World.getInstance().addObject(npc.getObjectId(), npc);
			}
			rs.close();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void loadStaticObjects() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("SELECT * FROM staticobjects")) {
			ResultSet rs = statement.executeQuery();
			while (rs.next()) {
				StaticObject obj = StaticObject.loadStaticObject(rs);
				World.getInstance().addObject(obj.getObjectId(), obj);
			}
			rs.close();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void saveNpcs() {
		for(GameObject obj: World.getInstance().getObjects().values()){
			if (obj instanceof NpcObject){
				((NpcObject) obj).saveNpc();
			} else if (obj instanceof StaticObject){
				((StaticObject) obj).saveStaticObject();
			}
		}
	}
	/**
	 * Применени биома к одной карте Определяем среднюю высоту карты над уровнем
	 * моря, и относительно этого выбираем биом
	 * 
	 * */
	public void setBiom(Map map) {
		int height = 0;
		for (int i = 0; i < Config.mapWidth; i++)
			height += map.getGroundLine()[i];
		height /= Config.mapWidth;
		height = waterLine - height;
		Biom choosed = DefManager.bioms.get(0);
		for (Biom b : DefManager.bioms.values()) {
			// if (b.minHeight >= height && height <= b.maxHeight)
			if (height >= b.minHeight && height <= b.maxHeight) {
				choosed = b;
				break;
			}
		}
		map.setBiomId(choosed.id);
	}

	public void applyBiom(Map map) {
		for (int x = 0; x < Config.mapWidth; x++) {
			for (int y = map.getGroundLine()[x]; y < Config.mapHeight; y++) {
				AbstractMapObject mapObject = new GroundObject(x
						* Config.tileSize
						+ (map.index * Config.mapWidth * Config.tileSize), y
						* Config.tileSize, (short) x, (short) y,
						getRandomGroundDef(y - map.getGroundLine()[x],
								bioms.get(map.getBiomId())), map);
				map.getMap()[x][y] = mapObject;
			}
		}

		Map other = null;
		GroundDef thisMap, otherMap, choosedGround = null;
		if (getMaps().get(map.index + 1) != null
				&& getMaps().get(map.index + 1).getBiomId() != -1) {
			other = getMaps().get(map.index + 1);
			for (int x = Config.mapWidth - Config.tileCountForBiomMixing; x < Config.mapWidth; x++) {
				float current = Config.mapWidth - x;
				float percent = Config.MAX_CHANCE / 2 - current
						/ (Config.tileCountForBiomMixing * 2 / 100f) * 1000;
				for (int y = map.getGroundLine()[x]; y < Config.mapHeight; y++) {
					int chance = MathUtil.rnd.nextInt(Config.MAX_CHANCE);
					thisMap = getRandomGroundDef(y - map.getGroundLine()[x],
							bioms.get(map.getBiomId()));
					otherMap = getRandomGroundDef(y - map.getGroundLine()[x],
							bioms.get(other.getBiomId()));
					if (chance < percent)
						choosedGround = otherMap;
					else
						choosedGround = thisMap;
					AbstractMapObject mapObject = new GroundObject(x
							* Config.tileSize
							+ (map.index * Config.mapWidth * Config.tileSize),
							y * Config.tileSize, (short) x, (short) y,
							choosedGround, map);
					map.getMap()[x][y] = mapObject;
				}
			}
		}
		if (getMaps().get(map.index - 1) != null
				&& getMaps().get(map.index - 1).getBiomId() != -1) {
			other = getMaps().get(map.index - 1);
			for (int x = 0; x < Config.tileCountForBiomMixing; x++) {
				float current = x;
				float percent = Config.MAX_CHANCE / 2 - current
						/ (Config.tileCountForBiomMixing * 2 / 100f) * 1000;
				for (int y = map.getGroundLine()[x]; y < Config.mapHeight; y++) {
					int chance = MathUtil.rnd.nextInt(Config.MAX_CHANCE);
					thisMap = getRandomGroundDef(y - map.getGroundLine()[x],
							bioms.get(map.getBiomId()));
					otherMap = getRandomGroundDef(y - map.getGroundLine()[x],
							bioms.get(other.getBiomId()));
					if (chance < percent)
						choosedGround = otherMap;
					else
						choosedGround = thisMap;
					AbstractMapObject mapObject = new GroundObject(x
							* Config.tileSize
							+ (map.index * Config.mapWidth * Config.tileSize),
							y * Config.tileSize, (short) x, (short) y,
							choosedGround, map);
					map.getMap()[x][y] = mapObject;
				}
			}
		}
		createFlora(map);
		createFauna(map);
		createTrees(map);
	}

	private void createTrees(Map map) {
		Biom b = bioms.get(map.getBiomId());
		if (b.trees.size() == 0)
			return;
		ArrayList<StaticObject> trees = new ArrayList<StaticObject>();
		for (BiomObjectDef def : b.trees) {
			boolean finded = false;
			int count;
			if (def.minCount == def.maxCount)
				count = def.minCount;
			else
				count = def.minCount
						+ MathUtil.rnd.nextInt(def.maxCount - def.minCount);

			// int diff = Config.mapWidth/count;
			// int x = diff;
			int x = 0;
			for (int i = 0; i < count; i++) {
				while (!finded) {
					finded = true;
					x = 1 + MathUtil.rnd.nextInt(Config.mapWidth - 2);
					for (StaticObject obj : trees) {
						if (Math.abs(obj.getGroundTileX() - x) <= 3) {
							finded = false;
							break;
						}
					}
				}
				NpcDef d = DefManager.npcDefs.get(def.npcDefId);
				Tree obj = null;
				if (d.getStringProperty("instance") == "")
					obj = new Tree(World.getNextId(), d.id);
				else {
					try {
						obj = (Tree) Class.forName(
								"terra.model.object."
										+ d.getStringProperty("instance"))
								.newInstance();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				obj.applyDef(d);
				obj.setX(x * Config.tileSize
						+ (map.index * Config.mapWidth * Config.tileSize));
				obj.setY(map.getGroundLine()[x] * Config.tileSize
						- Config.tileSize);
				obj.setGroundTileX(x);
				obj.setGroundTileY(map.getGroundLine()[x]);
				obj.makeTree(map.getMap()[obj.getGroundTileX()][obj
						.getGroundTileY()]);
				map.getObjects().put(obj.getObjectId(),obj);
				World.getInstance().addObject(obj.getObjectId(), obj);
				obj.insertStaticObject();
				finded = false;
				trees.add(obj);
			}
		}
	}

	private void createFauna(Map map) {
		Biom b = bioms.get(map.getBiomId());

		if (b.fauna.size() == 0)
			return;
		for (BiomObjectDef def : b.fauna) {
			int count;
			if (def.minCount == def.maxCount)
				count = def.minCount;
			else
				count = def.minCount
						+ MathUtil.rnd.nextInt(def.maxCount - def.minCount);

			int x;
			x = MathUtil.rnd.nextInt(Config.mapWidth);
			for (int i = 0; i < count; i++) {
				NpcDef d = DefManager.npcDefs.get(def.npcDefId);
				SpawnPoint spawn = new SpawnPoint();
				NpcObject obj = ObjectManager.createNpc(d);
				obj.setSpawn(spawn);
				spawn.setDef(def.npcDefId);
				spawn.setMapIndex(map.index);
				spawn.setX(x * Config.tileSize
						+ (map.index * Config.mapWidth * Config.tileSize));
				spawn.setY(map.getGroundLine()[x] * Config.tileSize
						- Config.tileSize * 4);
				spawn.setSpawned(obj);
				obj.setX(x * Config.tileSize
						+ (map.index * Config.mapWidth * Config.tileSize));
				obj.setY(map.getGroundLine()[x] * Config.tileSize
						- Config.tileSize * 4);
				try {
					obj.checkLocation();
				} catch (Exception e) {
					System.out.println(obj.getX() + "___" + obj.getY() + " _ "
							+ obj.getMapIndex());
					e.printStackTrace();
				}
				World.getInstance().addObject(obj.getObjectId(), obj);
				obj.insertNpc();
				//map.getObjects().add(obj);
				x = MathUtil.rnd.nextInt(Config.mapWidth);
			}
		}
	}

	private void createFlora(Map map) {
		Biom b = bioms.get(map.getBiomId());

		if (b.flora.size() == 0)
			return;
		for (BiomObjectDef def : b.flora) {
			int count;
			if (def.minCount == def.maxCount)
				count = def.minCount;
			else
				count = def.minCount
						+ MathUtil.rnd.nextInt(def.maxCount - def.minCount);
			int x;
			x = MathUtil.rnd.nextInt(Config.mapWidth);
			for (int i = 0; i < count; i++) {
				NpcDef d = DefManager.npcDefs.get(def.npcDefId);
				StaticObject obj = null;
				if (d.getStringProperty("instance") == "")
					obj = new Flora(d);
				else {
					try {
						obj = (StaticObject) Class.forName(
								"terra.model.object."
										+ d.getStringProperty("instance"))
								.newInstance();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				obj.setDefId(def.npcDefId);
				obj.applyDef();
				obj.setX(x * Config.tileSize
						+ (map.index * Config.mapWidth * Config.tileSize));
				obj.setY(map.getGroundLine()[x] * Config.tileSize
						- Config.tileSize);
				// obj.setMapIndex(map.x);
				obj.setGroundTileX(x);
				obj.setGroundTileY(map.getGroundLine()[x] - 1);
				World.getInstance().addObject(obj.getObjectId(), obj);
				obj.insertStaticObject();
				map.getObjects().put(obj.getObjectId(),obj);
				x = MathUtil.rnd.nextInt(Config.mapWidth);
			}
		}
	}

	private GroundDef getRandomGroundDef(final int height, Biom b) {
		// получаем рандомное число от 0 до 100 000
		int chance = MathUtil.rnd.nextInt(Config.MAX_CHANCE);
		// список, в котором хранятся все типы, подходящие под данную высоту
		ArrayList<BiomGroundDef> tempList = new ArrayList<BiomGroundDef>();
		for (BiomGroundDef def : b.grounds)
			// если высота больше или равна минимальной, и меньше и равна
			// максимальной, то добавить в список
			if (height >= def.minHeight && height <= def.maxHeight)
				tempList.add(def);

		// если в списке всего 1 тип, игнорируем его шанс и выбираем его
		if (tempList.size() == 1)
			return groundDefs.get(tempList.get(0).groundId);

		BiomGroundDef[] defs = new BiomGroundDef[tempList.size()];
		tempList.toArray(defs);

		Arrays.sort(defs, new Comparator<BiomGroundDef>() {
			@Override
			public int compare(BiomGroundDef o1, BiomGroundDef o2) {
				int result = 0;
				// if (o1.chance > o2.chance)
				// 2<3
				// два меньше трех
				if (getPercentByHeight(o1, height) > getPercentByHeight(o2,
						height))
					result = 1;
				else
					result = -1;
				if (o1.isRes)
					result = -1;
				else if (o2.isRes)
					result = 1;
				return result;
			}
		});
		// если в списке всего 1 тип - берем его, не смотря на его шанс

		// в другом случае - сравниваем шанс каждого типа земли с нашим
		// массив типов упорядочен по шансам, от меньших к бОльшим.

		for (BiomGroundDef def : defs) {
			if (chance < getPercentByHeight(def, height))
				// if (chance < def.chance)
				return groundDefs.get(def.groundId);
		}
		// System.out.println(" not! " + defs.length);
		if (defs.length >= 1)
			return groundDefs.get(defs[defs.length - 1].groundId);
		// в случае ошибки, берем стандартный тип
		return groundDefs.get(0);
	}

	// Максимальный шанс должен достигаться в середине высоты генерации грунта
	// К краям выравниваться к 0
	int getPercentByHeight(BiomGroundDef def, int height) {
		// максимальный шанс, достижим при diff = 0
		int maxChance = def.chance;
		if (maxChance <= 10000)
			return maxChance;
		// разница в уровне высоты между центром генерации этого грунта и
		// заданной высотой.
		int diff = Math.abs(height - ((def.maxHeight - def.minHeight) / 2));
		if (diff == 0)
			return maxChance;
		// максимально возможная разница между центром генерации этого грунта и
		// его краями.
		// это должно быть 100 процентами, относительно диффа.
		int max = (def.maxHeight - def.minHeight) / 2;

		float perc = max / 100f;
		// float perc2 = 100;
		// if (diff != 0)
		float perc2 = (int) (diff / perc);
		// if (debug)
		// System.out.println("getPercentByHeight " + def.groundId + "   __    "
		// + height + "   __    " + max +"   __    "+ diff
		// +"   __    "+(100f-perc2));

		int ret = (int) ((maxChance / 100f) * (100f - perc2));
		return ret;
	}

	public int getMapIndex(int x) {
		int cx = x % Config.mapWidth;
		int mapCount = (x - cx) / Config.mapWidth;
		if (mapCount == 0 && x < 0)
			return -1;
		if (x < 0 && cx != 0)
			return mapCount - 1;
		return mapCount;
	}

	public AbstractMapObject getTileFromTiles(int x, int y) {
		int mapX = 0, mapY = 0, mapIndex = 0;
		mapIndex = getMapIndex(x);
		mapX = x - mapIndex * Config.mapWidth;
		mapY = y;
		if (mapY < 0)
			mapY = 0;
		if (mapY >= Config.mapHeight)
			mapY = Config.mapHeight - 2;
		if (mapX < 0)
			mapX = -mapX;
		if (mapX >= Config.mapWidth)
			mapX = Config.mapWidth - 1;
		if (getMaps().containsKey(mapIndex))
			return getMaps().get(mapIndex).getMap()[mapX][mapY];
		return null;
	}

	@Override
	public void run() {
		initManager();
		// while(true)
		// {
		// //updateMaps();
		//
		// //minIndex = getMapIndex(game.getTiledOffsetX() -
		// Config.actualMapSizeAdd/2);
		// //maxIndex = getMapIndex(game.getTiledOffsetX() -
		// Config.actualMapSizeAdd/2+actualMapWidth);
		// //if (oldMinIndex != minIndex || oldMaxIndex != maxIndex)
		// // ObjectManager.mapChanged(minIndex, maxIndex);
		// try {
		// Thread.sleep(1000/10);
		// } catch (InterruptedException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		// }
	}

	/*
	 * public void updateMaps() { if (checkTime < System.currentTimeMillis()) {
	 * //currentMap = getMapIndex((int) (game.player.getX()/Config.tileSize));
	 * //тут идёт код на проверку надобности дальних карт // нужно ли догрузить,
	 * или создать карту. //System.out.println(" asd " + (minMapIndex+2) +
	 * "  asdas   " + minIndex); if (minMapIndex+Config.distanceForLoad >=
	 * minIndex) { if (waitingMaps.contains(minMapIndex-1)) return;
	 * //System.out.println("neeed to load\\create " + (minMapIndex-1));
	 * waitingMaps.add(minMapIndex-1); if (mapExists(minMapIndex-1))
	 * ThreadPoolManager.getInstance().executeLoad(new
	 * MapLoader(minMapIndex-1)); else
	 * ThreadPoolManager.getInstance().executeCreate(new
	 * MapCreater(minMapIndex-1));
	 * 
	 * 
	 * }
	 * 
	 * if (maxMapIndex-Config.distanceForLoad <= maxIndex) { if
	 * (waitingMaps.contains(maxMapIndex+1)) return;
	 * //System.out.println("neeed to load\\create " + (maxMapIndex+1));
	 * waitingMaps.add(maxMapIndex+1); if (mapExists(maxMapIndex+1))
	 * ThreadPoolManager.getInstance().executeLoad(new
	 * MapLoader(maxMapIndex+1)); else
	 * ThreadPoolManager.getInstance().executeCreate(new
	 * MapCreater(maxMapIndex+1)); }
	 * 
	 * // тут идет код на сохранение и удаление из памяти дальних (не
	 * используемых карт) if (maxMapIndex - currentMap > Config.distanceForSave)
	 * { if (waitingMaps.contains(maxMapIndex)) return;
	 * ThreadPoolManager.getInstance().scheduleSave(new
	 * MapSaver(getMaps().get(maxMapIndex),true),GameState.rnd.nextInt(50)*100);
	 * waitingMaps.add(maxMapIndex); } int diff = minMapIndex -currentMap; if
	 * (diff < 0) diff = - diff; if (diff > Config.distanceForSave) { if
	 * (waitingMaps.contains(minMapIndex)) return;
	 * ThreadPoolManager.getInstance().scheduleSave(new
	 * MapSaver(getMaps().get(minMapIndex),true),GameState.rnd.nextInt(50)*100);
	 * waitingMaps.add(minMapIndex); } checkTime = System.currentTimeMillis() +
	 * 500L; } }
	 */

	/**
	 * простая проверка на существование файла, пока что считаем, что если файл
	 * существует, то этого достаточно.
	 * 
	 * @param index
	 * @return
	 */

	boolean mapExists(int index) {
		File f = new File("map" + File.separator + index + ".map");
		return f.exists();
	}

	public int randomRange(int low, int high) {
		return (int) Math.floor(Math.random() * (1 + high - low)) + low;
	}

	public void setMaps(ConcurrentHashMap<Integer, Map> maps) {
		this.maps = maps;
	}

	public ConcurrentHashMap<Integer, Map> getMaps() {
		return maps;
	}

	public static MapManager instance = new MapManager();

	public static MapManager getInstance() {
		return instance;
	}

	public void createMap(int index) {
		Map map = new Map();
		map.index = index;
		map.setMap(new AbstractMapObject[Config.mapWidth][Config.mapHeight]);

		int terrainDestruct = 2 + MathUtil.rnd.nextInt(3);
		int meter = Config.mapHeight / 2 - 64;
		int lastMeter;
		// переменная otherWay создана для случая с картами, которые нужно
		// создавать не слева направо, а справа налево.
		boolean otherWay = false;
		if (!getMaps().containsKey((index - 1)) && init)
			otherWay = true;
		if (getMaps().containsKey((index - 1)))
			meter = getMaps().get(index - 1).container.meterRight;

		if (getMaps().containsKey(index + 1))
			meter = maps.get(index + 1).container.meterLeft;
		lastMeter = meter;
		if (otherWay)
			map.container.meterRight = lastMeter;
		else
			map.container.meterLeft = lastMeter;

		int curX = 0;
		if (otherWay)
			curX = Config.mapWidth - 1;
		int b = randomRange(-terrainDestruct, terrainDestruct);
		int a = randomRange(-b, b);
		for (int w = 0; w < Config.mapWidth; w++) {
			map.getGroundLine()[curX] = meter;
			meter = Config.mapHeight;
			if (meter == Config.mapHeight) {
				if (otherWay)
					curX--;
				else
					curX++;
				if (randomRange(0, 100) < 20)
					b = randomRange(-terrainDestruct, terrainDestruct);
				if (randomRange(0, 100) < 50)
					a = randomRange(-b, b);
				lastMeter += a;
				meter = lastMeter;
				terrainDestruct = 2 + MathUtil.rnd.nextInt(3);
			}
		}
		if (otherWay)
			map.container.meterLeft = lastMeter;
		else
			map.container.meterRight = lastMeter;
		getWaitingMaps().remove((Integer) map.index);
		mapAdded(map.index);
		getMaps().put(map.index, map);
		WeatherManager.getInstance().mapCreated(map);
		map.save();
	}

	public void mapAdded(int index) {
		if (minMapIndex - 1 == index)
			minMapIndex--;
		if (maxMapIndex + 1 == index)
			maxMapIndex++;
	}

	public void mapRemoved(int x) {
		if (minMapIndex == x)
			minMapIndex++;
		if (maxMapIndex == x)
			maxMapIndex--;
	}

	public void setWaitingMaps(ArrayList<Integer> waitingMaps) {
		this.waitingMaps = waitingMaps;
	}

	public ArrayList<Integer> getWaitingMaps() {
		return waitingMaps;
	}

	// находим блок
	// заменяем на пустоту
	// исходня из ширины, продлеваем пустоту
	// идём ниже на 1 блок исходя из directions

	/**  */
	public void generateVerticalCave(Map map) {
		int chance = MathUtil.rnd.nextInt(Config.MAX_CHANCE);
		// if (chance > 80000)
		// return;
		int minWidth = 2;
		int maxWidth = Config.mapWidth / 4;
		int length = 50 + MathUtil.rnd.nextInt(130);
		int roughness = 50;
		int windyness = 90;
		int startX = Config.mapWidth / 2;
		int startY = map.getGroundLine()[startX];
		int x = startX;
		int y = startY;
		if (bioms.get(map.getBiomId()).name.equalsIgnoreCase("underwater"))
			y += 10;
		int width = 2 + MathUtil.rnd.nextInt(3);

		for (int i = 0; i < length; i++) {
			if (y >= Config.mapHeight - 1)
				break;
			int rnd = MathUtil.rnd.nextInt(100);
			if (rnd < roughness) {
				int diff = -2 + MathUtil.rnd.nextInt(5);
				if (diff == 0)
					diff = 1;
				width += diff;
				if (width < minWidth)
					width = minWidth;
				if (width >= maxWidth)
					width = maxWidth;
			}
			rnd = MathUtil.rnd.nextInt(100);
			if (rnd <= windyness) {
				int diff = -2 + MathUtil.rnd.nextInt(5);
				if (diff == 0)
					diff = 1;
				x += diff;
				if (x < 0)
					x = 0;
				if (x >= Config.mapWidth)
					x = Config.mapWidth - minWidth;
			}
			for (int w = 0; w < width; w++) {
				int resX = x + w;
				MapObject obj;
				if (x + w >= Config.mapWidth) {
					if (getMaps().get(map.index + 1) == null)
						continue;
					AbstractMapObject old = getMaps().get(map.index + 1)
							.getMap()[x + w - Config.mapWidth][y];
					if (old != null && old instanceof GroundObject)
						obj = new MapObject((GroundObject) old);
					else if (old != null && old instanceof MapObject)
						obj = new MapObject((MapObject) old);
					else
						obj = new MapObject(
								((x + w - Config.mapWidth) + (map.index + 1)
										* Config.mapWidth)
										* Config.tileSize, y * Config.tileSize,
								(short) (x + w - Config.mapWidth), (short) y,
								groundDefs.get(-1), getMaps()
										.get(map.index + 1));
					getMaps().get(map.index + 1).getMap()[x + w
							- Config.mapWidth][y] = obj;
					// continue;
				} else {
					AbstractMapObject old = map.getMap()[resX][y];
					if (old != null && old instanceof GroundObject)
						obj = new MapObject((GroundObject) old);
					else if (old != null && old instanceof MapObject)
						obj = new MapObject((MapObject) old);
					else
						obj = new MapObject(((resX) + (map.index)
								* Config.mapWidth)
								* Config.tileSize, y * Config.tileSize,
								(short) (resX), (short) y, groundDefs.get(-1),
								getMaps().get(map.index));

					map.getMap()[resX][y] = obj;
				}
				// obj.isCave = true;
				if (w == 0 || w == width - 1) {
					if (MathUtil.rnd.nextInt(100) < 5) {
						// FIXME факелы должны крепиться к стенам, т.е. краям
						// пещеры
						LightObject light = new LightObject();
						light.setX(obj.getX());
						light.setY(obj.getY());
						light.setImage("light0");
						light.setWidth(Config.tileSize);
						light.setHeight(Config.tileSize);
						light.setDefId(25);
						light.setTile(obj);
						light.init(DefManager.allItems.get(light.getDefId()));
						map.getObjects().put(light.getObjectId(),light);
						World.getInstance().addObject(light.getObjectId(),
								light);
						light.insertStaticObject();
					}
				}
			}
			y++;
		}
		chance = MathUtil.rnd.nextInt(Config.MAX_CHANCE);
		if (chance > 50000)
			return;
		// ((MapObject) map.map[x][y]).setMass(300);
	}

	/**
	 * Ищем в каждой карте карте по n квадратов, где y <= линии воды После этого
	 * производим симуляцию (чтобы вода равномерно заполнила необходимое
	 * пространство)
	 * */
	public boolean generateWater(Map map) {
		//System.out.println("waterLine " + waterLine);
		for (int x = 0; x < Config.mapWidth; x++) {
			if (waterLine > map.getGroundLine()[x])
				continue;
			for (int i = waterLine; i < map.getGroundLine()[x]; i++) {
				if (i >= Config.mapHeight)
					continue;
				if (map.getMap()[x][i] == null) {
					MapObject obj = new MapObject((x + map.index
							* Config.mapWidth)
							* Config.tileSize, i * Config.tileSize, (short) x,
							(short) i, groundDefs.get(-1), map);
					obj.setMass(1f);
					obj.setNewMass(1f);
					map.getMap()[x][i] = obj;
				} else if (map.getMap()[x][i].getType() != MapObject.TYPE_GROUND) {
					((MapObject) map.getMap()[x][i]).setMass(1f);
					((MapObject) map.getMap()[x][i]).setNewMass(1f);
				}
			}
		}
		return true;
	}

	public Map getMap(int mapIndex) {
		return getMaps().get(mapIndex);
	}

}
